package edu.up.firesawakening;

import java.util.Random;
import java.util.Vector;

import edu.up.firesawakening.TacticsGame.Move;
import edu.up.firesawakening.TacticsGame.*;
import edu.up.game.GameAction;
import edu.up.game.GameComputerPlayer;

/**
 * A player that is controlled by the AI. Responds to the game state accordingly
 * each turn.
 * 
 * @author Alex Hanemann
 * @version 20 November 2012
 */
public abstract class TacticsComputerPlayer extends GameComputerPlayer {

	/**
	 * The game the player can use
	 */
	protected TacticsGame m_tg;

	/**
	 * The map
	 */
	protected Tile[][] m_map;

	/**
	 * Player id
	 */
	protected int m_id;

	/**
	 * Generates a move to make based on the current game state
	 * 
	 * @return the desired move
	 */
	@Override
	public final GameAction calculateMove() {
		m_tg = (TacticsGame) game;
		m_map = m_tg.getMap();
		m_id = m_tg.whoseTurn();
		Random rand = new Random();

		Vector<TacticsAction> actionList = getActionsFromState(m_tg);

		Vector<TacticsAction> finalList = new Vector<TacticsAction>();

		// If this happens, no moves were found and the turn is over
		if (actionList.isEmpty())
			return new TacticsEndTurnAction(m_id);

		TacticsGame newState;
		double topVal = -999;

		for (TacticsAction ta : actionList) {
			// Get state value if we take this action
			newState = (TacticsGame) m_tg.getPlayerState(m_id);
			newState.applyAction(ta);
			ta.setValue(evaluateState(newState));

			// Clear the list if we have a new best state
			if (ta.getValue() > topVal) {
				finalList.clear();
				topVal = ta.getValue();
			}

			// Add the new state to the list if it is good enough
			if (ta.getValue() >= topVal) {
				finalList.add(ta);
			}
		}

		// If there were no positive utility options, we should just end the
		// turn
		if (topVal < 0)
			return new TacticsEndTurnAction(m_id);

		// Return the move. Random if we have multiple possible
		return finalList.get(rand.nextInt(finalList.size()));
	}

	/**
	 * @param state
	 *            state to get units from
	 * @param mine
	 *            true will cause this method to get this player's units, false
	 *            the other players
	 * @return units owned by the player in this state
	 */
	protected Vector<Tile> getUnitsFromState(TacticsGame state, boolean mine) {
		Vector<Tile> units = new Vector<Tile>();

		Tile[][] map = state.getMap();

		for (int x = 0; x < map.length; x++) {
			for (int y = 0; y < map[0].length; y++) {
				if (map[x][y].isOccupied()) {
					if ((mine && map[x][y].getUnit().getOwner() == m_id)
							|| (!mine && map[x][y].getUnit().getOwner() != m_id)) {
						units.add(map[x][y]);
					}
				}
			}
		}

		return units;
	}

	/**
	 * @param tile
	 *            tile to get actions from
	 * @return actions the unit on the tile has available
	 */
	protected Vector<TacticsAction> getActionsFromUnitTile(Tile tile) {
		Move[][] moves = m_tg.availableActions(tile);
		Vector<Move> moveList = new Vector<Move>();
		Vector<TacticsAction> actionList = new Vector<TacticsAction>();

		// Get all moves for this unit
		for (int x = 0; x < moves.length; x++) {
			for (int y = 0; y < moves[0].length; y++) {
				if (moves[x][y].move == MoveType.MOVE
						|| moves[x][y].move == MoveType.ATTACK)
					moveList.add(moves[x][y]);
			}
		}

		// Get the possible moves in Action form
		for (Move m : moveList) {
			if (m.move == MoveType.MOVE) {
				actionList.add(new TacticsMoveAction(m_id, m.start, m.target,
						m.cost));
			} else {
				actionList.add(new TacticsAttackAction(m_id, m.start, m.target,
						m.cost));
			}
		}

		return actionList;
	}

	/**
	 * @param state
	 *            the state to get actions from
	 * @return all possible actions this player could take
	 */
	protected Vector<TacticsAction> getActionsFromState(TacticsGame state) {
		Vector<Tile> units = getUnitsFromState(m_tg, true);

		Vector<TacticsAction> actionList = new Vector<TacticsAction>();

		for (Tile t : units) {
			actionList.addAll(getActionsFromUnitTile(t));
		}

		return actionList;
	}

	/**
	 * Determines the value of a state from this player's perspective.
	 * 
	 * @param state
	 *            the state to evaluate
	 * @return a number representing the state's value
	 */
	protected abstract double evaluateState(TacticsGame state);
}
